import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import missingno as msno
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from xgboost import XGBClassifier
from sklearn.metrics import accuracy_score, precision_score, recall_score, roc_auc_score, confusion_matrix, roc_curve
import shap
import pickle
path = 'C:\\Users\\valen\\Desktop\\data\\dataset.csv'
data = pd.read_csv(path)
a) Dimensiune:
data.shape
(10127, 21)
b) Previzualizarea datelor:
#folosesc functia head()
data.head(20)
| CLIENTNUM | Attrition_Flag | Customer_Age | Gender | Dependent_count | Education_Level | Marital_Status | Income_Category | Card_Category | Months_on_book | ... | Months_Inactive_12_mon | Contacts_Count_12_mon | Credit_Limit | Total_Used_Bal | Total_Unused_Bal | Total_Amt_Chng_Q4_Q1 | Total_Trans_Amt | Total_Trans_Ct | Total_Ct_Chng_Q4_Q1 | Avg_Utilization_Ratio | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 768805383 | Existing Customer | 45.0 | M | 3.0 | High School | Married | $60K - $80K | Blue | 39 | ... | 1 | 3 | 12691.0 | 777 | 11914.0 | 1.335 | 1144 | 42 | 1.625 | 0.061 |
| 1 | 818770008 | Existing Customer | 49.0 | F | 5.0 | Graduate | Single | Less than $40K | Blue | 44 | ... | 1 | 2 | 8256.0 | 864 | 7392.0 | 1.541 | 1291 | 33 | 3.714 | 0.105 |
| 2 | 713982108 | Existing Customer | 51.0 | M | 3.0 | Graduate | Married | $80K - $120K | Blue | 36 | ... | 1 | 0 | 3418.0 | 0 | 3418.0 | 2.594 | 1887 | 20 | 2.333 | 0.000 |
| 3 | 769911858 | Existing Customer | 40.0 | F | 4.0 | High School | NaN | Less than $40K | Blue | 34 | ... | 4 | 1 | 3313.0 | 2517 | 796.0 | 1.405 | 1171 | 20 | 2.333 | 0.760 |
| 4 | 709106358 | Existing Customer | 40.0 | M | 3.0 | Uneducated | Married | $60K - $80K | Blue | 21 | ... | 1 | 0 | 4716.0 | 0 | 4716.0 | 2.175 | 816 | 28 | 2.500 | 0.000 |
| 5 | 713061558 | Existing Customer | 44.0 | M | 2.0 | Graduate | Married | $40K - $60K | Blue | 36 | ... | 1 | 2 | 4010.0 | 1247 | 2763.0 | 1.376 | 1088 | 24 | 0.846 | 0.311 |
| 6 | 810347208 | Existing Customer | 51.0 | M | 4.0 | NaN | Married | $120K + | Gold | 46 | ... | 1 | 3 | 34516.0 | 2264 | 32252.0 | 1.975 | 1330 | 31 | 0.722 | 0.066 |
| 7 | 818906208 | Existing Customer | 32.0 | M | 0.0 | High School | NaN | $60K - $80K | Silver | 27 | ... | 2 | 2 | 29081.0 | 1396 | 27685.0 | 2.204 | 1538 | 36 | 0.714 | 0.048 |
| 8 | 710930508 | Existing Customer | 37.0 | M | 3.0 | Uneducated | Single | $60K - $80K | Blue | 36 | ... | 2 | 0 | 22352.0 | 2517 | 19835.0 | 3.355 | 1350 | 24 | 1.182 | 0.113 |
| 9 | 719661558 | Existing Customer | 48.0 | M | 2.0 | Graduate | Single | $80K - $120K | Blue | 36 | ... | 3 | 3 | 11656.0 | 1677 | 9979.0 | 1.524 | 1441 | 32 | 0.882 | 0.144 |
| 10 | 708790833 | Existing Customer | 42.0 | M | 5.0 | Uneducated | NaN | $120K + | Blue | 31 | ... | 3 | 2 | 6748.0 | 1467 | 5281.0 | 0.831 | 1201 | 42 | 0.680 | 0.217 |
| 11 | 710821833 | Existing Customer | 65.0 | M | 1.0 | NaN | Married | $40K - $60K | Blue | 54 | ... | 2 | 3 | 9095.0 | 1587 | 7508.0 | 1.433 | 1314 | 26 | 1.364 | 0.174 |
| 12 | 710599683 | Existing Customer | 56.0 | M | 1.0 | College | Single | $80K - $120K | Blue | 36 | ... | 6 | 0 | 11751.0 | 0 | 11751.0 | 3.397 | 1539 | 17 | 3.250 | 0.000 |
| 13 | 816082233 | Existing Customer | 35.0 | M | 3.0 | Graduate | NaN | $60K - $80K | Blue | 30 | ... | 1 | 3 | 8547.0 | 1666 | 6881.0 | 1.163 | 1311 | 33 | 2.000 | 0.195 |
| 14 | 712396908 | Existing Customer | 57.0 | F | 2.0 | Graduate | Married | Less than $40K | Blue | 48 | ... | 2 | 2 | 2436.0 | 680 | 1756.0 | 1.190 | 1570 | 29 | 0.611 | 0.279 |
| 15 | 714885258 | Existing Customer | 44.0 | M | 4.0 | NaN | NaN | $80K - $120K | Blue | 37 | ... | 1 | 2 | 4234.0 | 972 | 3262.0 | 1.707 | 1348 | 27 | 1.700 | 0.230 |
| 16 | 709967358 | Existing Customer | 48.0 | M | 4.0 | Post-Graduate | Single | $80K - $120K | Blue | 36 | ... | 2 | 3 | 30367.0 | 2362 | 28005.0 | 1.708 | 1671 | 27 | 0.929 | 0.078 |
| 17 | 753327333 | Existing Customer | 41.0 | M | 3.0 | NaN | Married | $80K - $120K | Blue | 34 | ... | 4 | 1 | 13535.0 | 1291 | 12244.0 | 0.653 | 1028 | 21 | 1.625 | 0.095 |
| 18 | 806160108 | Existing Customer | 61.0 | M | 1.0 | High School | Married | $40K - $60K | Blue | 56 | ... | 2 | 3 | 3193.0 | 2517 | 676.0 | 1.831 | 1336 | 30 | 1.143 | 0.788 |
| 19 | 709327383 | Existing Customer | 45.0 | F | 2.0 | Graduate | Married | abc | Blue | 37 | ... | 1 | 2 | 14470.0 | 1157 | 13313.0 | 0.966 | 1207 | 21 | 0.909 | 0.080 |
20 rows × 21 columns
c) Afișarea coloanelor și a tipurilor de date din acestea.
coloane = data.columns
print(coloane)
Index(['CLIENTNUM', 'Attrition_Flag', 'Customer_Age', 'Gender',
'Dependent_count', 'Education_Level', 'Marital_Status',
'Income_Category', 'Card_Category', 'Months_on_book',
'Total_Relationship_Count', 'Months_Inactive_12_mon',
'Contacts_Count_12_mon', 'Credit_Limit', 'Total_Used_Bal',
'Total_Unused_Bal', 'Total_Amt_Chng_Q4_Q1', 'Total_Trans_Amt',
'Total_Trans_Ct', 'Total_Ct_Chng_Q4_Q1', 'Avg_Utilization_Ratio'],
dtype='object')
data.dtypes
CLIENTNUM int64 Attrition_Flag object Customer_Age float64 Gender object Dependent_count float64 Education_Level object Marital_Status object Income_Category object Card_Category object Months_on_book int64 Total_Relationship_Count int64 Months_Inactive_12_mon int64 Contacts_Count_12_mon int64 Credit_Limit float64 Total_Used_Bal int64 Total_Unused_Bal float64 Total_Amt_Chng_Q4_Q1 float64 Total_Trans_Amt int64 Total_Trans_Ct int64 Total_Ct_Chng_Q4_Q1 float64 Avg_Utilization_Ratio float64 dtype: object
Se poate observa că există 21 de coloane care furnizează diverse informații despre un client bancar, cum ar fi vârsta, venitul, nivelul de educație, starea civilă, etc. Datele întâlnite în aceste coloane pot fi sub formă de float, int sau object.
d) Aflarea informațiilor setului de date
#folosesc functia info()
data.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 10127 entries, 0 to 10126 Data columns (total 21 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 CLIENTNUM 10127 non-null int64 1 Attrition_Flag 10127 non-null object 2 Customer_Age 10124 non-null float64 3 Gender 10127 non-null object 4 Dependent_count 10122 non-null float64 5 Education_Level 8608 non-null object 6 Marital_Status 9378 non-null object 7 Income_Category 10125 non-null object 8 Card_Category 10127 non-null object 9 Months_on_book 10127 non-null int64 10 Total_Relationship_Count 10127 non-null int64 11 Months_Inactive_12_mon 10127 non-null int64 12 Contacts_Count_12_mon 10127 non-null int64 13 Credit_Limit 10127 non-null float64 14 Total_Used_Bal 10127 non-null int64 15 Total_Unused_Bal 10127 non-null float64 16 Total_Amt_Chng_Q4_Q1 10127 non-null float64 17 Total_Trans_Amt 10127 non-null int64 18 Total_Trans_Ct 10127 non-null int64 19 Total_Ct_Chng_Q4_Q1 10127 non-null float64 20 Avg_Utilization_Ratio 10127 non-null float64 dtypes: float64(7), int64(8), object(6) memory usage: 1.6+ MB
e) Vizualizarea proprietăților statistice ale setului de date
data.describe()
| CLIENTNUM | Customer_Age | Dependent_count | Months_on_book | Total_Relationship_Count | Months_Inactive_12_mon | Contacts_Count_12_mon | Credit_Limit | Total_Used_Bal | Total_Unused_Bal | Total_Amt_Chng_Q4_Q1 | Total_Trans_Amt | Total_Trans_Ct | Total_Ct_Chng_Q4_Q1 | Avg_Utilization_Ratio | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| count | 1.012700e+04 | 10124.000000 | 10122.000000 | 10127.000000 | 10127.000000 | 10127.000000 | 10127.000000 | 10127.000000 | 10127.000000 | 10127.000000 | 10127.000000 | 10127.000000 | 10127.000000 | 10127.000000 | 10127.000000 |
| mean | 7.391776e+08 | 46.326057 | 2.345781 | 35.928409 | 3.812580 | 2.341167 | 2.455317 | 8631.953698 | 1162.814061 | 7469.139637 | 0.759941 | 4404.086304 | 64.858695 | 0.712222 | 0.274894 |
| std | 3.690378e+07 | 8.017889 | 1.298908 | 7.986416 | 1.554408 | 1.010622 | 1.106225 | 9088.776650 | 814.987335 | 9090.685324 | 0.219207 | 3397.129254 | 23.472570 | 0.238086 | 0.275691 |
| min | 7.080821e+08 | 26.000000 | 0.000000 | 13.000000 | 1.000000 | 0.000000 | 0.000000 | 1438.300000 | 0.000000 | 3.000000 | 0.000000 | 510.000000 | 10.000000 | 0.000000 | 0.000000 |
| 25% | 7.130368e+08 | 41.000000 | 1.000000 | 31.000000 | 3.000000 | 2.000000 | 2.000000 | 2555.000000 | 359.000000 | 1324.500000 | 0.631000 | 2155.500000 | 45.000000 | 0.582000 | 0.023000 |
| 50% | 7.179264e+08 | 46.000000 | 2.000000 | 36.000000 | 4.000000 | 2.000000 | 2.000000 | 4549.000000 | 1276.000000 | 3474.000000 | 0.736000 | 3899.000000 | 67.000000 | 0.702000 | 0.176000 |
| 75% | 7.731435e+08 | 52.000000 | 3.000000 | 40.000000 | 5.000000 | 3.000000 | 3.000000 | 11067.500000 | 1784.000000 | 9859.000000 | 0.859000 | 4741.000000 | 81.000000 | 0.818000 | 0.503000 |
| max | 8.283431e+08 | 73.000000 | 5.000000 | 56.000000 | 6.000000 | 6.000000 | 6.000000 | 34516.000000 | 2517.000000 | 34516.000000 | 3.397000 | 18484.000000 | 139.000000 | 3.714000 | 0.999000 |
data.describe(include = ['object'])
| Attrition_Flag | Gender | Education_Level | Marital_Status | Income_Category | Card_Category | |
|---|---|---|---|---|---|---|
| count | 10127 | 10127 | 8608 | 9378 | 10125 | 10127 |
| unique | 2 | 2 | 6 | 3 | 6 | 4 |
| top | Existing Customer | F | Graduate | Married | Less than $40K | Blue |
| freq | 8500 | 5358 | 3128 | 4687 | 3560 | 9436 |
data.describe(include = ['int64'])
| CLIENTNUM | Months_on_book | Total_Relationship_Count | Months_Inactive_12_mon | Contacts_Count_12_mon | Total_Used_Bal | Total_Trans_Amt | Total_Trans_Ct | |
|---|---|---|---|---|---|---|---|---|
| count | 1.012700e+04 | 10127.000000 | 10127.000000 | 10127.000000 | 10127.000000 | 10127.000000 | 10127.000000 | 10127.000000 |
| mean | 7.391776e+08 | 35.928409 | 3.812580 | 2.341167 | 2.455317 | 1162.814061 | 4404.086304 | 64.858695 |
| std | 3.690378e+07 | 7.986416 | 1.554408 | 1.010622 | 1.106225 | 814.987335 | 3397.129254 | 23.472570 |
| min | 7.080821e+08 | 13.000000 | 1.000000 | 0.000000 | 0.000000 | 0.000000 | 510.000000 | 10.000000 |
| 25% | 7.130368e+08 | 31.000000 | 3.000000 | 2.000000 | 2.000000 | 359.000000 | 2155.500000 | 45.000000 |
| 50% | 7.179264e+08 | 36.000000 | 4.000000 | 2.000000 | 2.000000 | 1276.000000 | 3899.000000 | 67.000000 |
| 75% | 7.731435e+08 | 40.000000 | 5.000000 | 3.000000 | 3.000000 | 1784.000000 | 4741.000000 | 81.000000 |
| max | 8.283431e+08 | 56.000000 | 6.000000 | 6.000000 | 6.000000 | 2517.000000 | 18484.000000 | 139.000000 |
data.describe(include = ['float64'])
| Customer_Age | Dependent_count | Credit_Limit | Total_Unused_Bal | Total_Amt_Chng_Q4_Q1 | Total_Ct_Chng_Q4_Q1 | Avg_Utilization_Ratio | |
|---|---|---|---|---|---|---|---|
| count | 10124.000000 | 10122.000000 | 10127.000000 | 10127.000000 | 10127.000000 | 10127.000000 | 10127.000000 |
| mean | 46.326057 | 2.345781 | 8631.953698 | 7469.139637 | 0.759941 | 0.712222 | 0.274894 |
| std | 8.017889 | 1.298908 | 9088.776650 | 9090.685324 | 0.219207 | 0.238086 | 0.275691 |
| min | 26.000000 | 0.000000 | 1438.300000 | 3.000000 | 0.000000 | 0.000000 | 0.000000 |
| 25% | 41.000000 | 1.000000 | 2555.000000 | 1324.500000 | 0.631000 | 0.582000 | 0.023000 |
| 50% | 46.000000 | 2.000000 | 4549.000000 | 3474.000000 | 0.736000 | 0.702000 | 0.176000 |
| 75% | 52.000000 | 3.000000 | 11067.500000 | 9859.000000 | 0.859000 | 0.818000 | 0.503000 |
| max | 73.000000 | 5.000000 | 34516.000000 | 34516.000000 | 3.397000 | 3.714000 | 0.999000 |
Variabila target va fi reprezentată de elementele din coloana "Attrition_Flag". Această coloană conține două valori: "Existing Customer" și "Attrited Customer". În cazul în care valoarea din coloana este "Existing Customer", înseamnă că acel client este activ și nu a părăsit banca. În schimb, dacă întâlnim valoarea "Attrited Customer", acest lucru indică un client care a renunțat la bancă. Putem utiliza această coloană pentru a evalua câți clienți ar putea părăsi banca și pentru a estima riscul asociat cu această situație.
a) Verificăm dacă variabila are valori nule
data['Attrition_Flag'].isnull().sum()
0
Lipsa valorilor nule indică faptul că fiecare client este fie activ, fie a părăsit sistemul băncii.
b) Verificăm câte valori unice avem și care sunt acestea
data['Attrition_Flag'].nunique()
2
data['Attrition_Flag'].unique()
array(['Existing Customer', 'Attrited Customer'], dtype=object)
c) Verificăm frecvența valorilor și procentajul acestora
data['Attrition_Flag'].value_counts()
Existing Customer 8500 Attrited Customer 1627 Name: Attrition_Flag, dtype: int64
Observăm că 8500 de clienți sunt încă în sistemul băncii noastre, iar 1627 l-au părăsit.
data['Attrition_Flag'].value_counts()/len(data)*100
Existing Customer 83.934038 Attrited Customer 16.065962 Name: Attrition_Flag, dtype: float64
Observăm că 83.93% din clienți au ales să nu părăsească banca, pe când 16.06% au făcut acest lucru.
d) Vizualizăm distribuția în variabila "Attrition Flag" printr-un grafic
fig,ax = plt.subplots(figsize = (8,6))
ax = sns.countplot(data = data, x = 'Attrition_Flag',palette = 'Set2')
plt.show()
Conform graficului, observăm că 8500 de clienți sunt încă în sistemul băncii noastre, pe când 1627 l-au părăsit.
e) Convertim valorile din variabila target "Attrition_Flag" din 'object' în 'int'
data['Attrition_Flag'] = data['Attrition_Flag'].map({'Attrited Customer':0,'Existing Customer':1})
data['Attrition_Flag'].value_counts()
1 8500 0 1627 Name: Attrition_Flag, dtype: int64
a) Explorarea variabilelor categorice
#Gasim variabilele categorice
data.dtypes
CLIENTNUM int64 Attrition_Flag int64 Customer_Age float64 Gender object Dependent_count float64 Education_Level object Marital_Status object Income_Category object Card_Category object Months_on_book int64 Total_Relationship_Count int64 Months_Inactive_12_mon int64 Contacts_Count_12_mon int64 Credit_Limit float64 Total_Used_Bal int64 Total_Unused_Bal float64 Total_Amt_Chng_Q4_Q1 float64 Total_Trans_Amt int64 Total_Trans_Ct int64 Total_Ct_Chng_Q4_Q1 float64 Avg_Utilization_Ratio float64 dtype: object
Variabilele categorice sunt cele de tip object, asadar pe acestea le selectam
categorical_variables = [col for col in data.columns if data[col].dtypes=='object']
print('In tabel exista',len(categorical_variables),'si acestea sunt:',categorical_variables)
In tabel exista 5 si acestea sunt: ['Gender', 'Education_Level', 'Marital_Status', 'Income_Category', 'Card_Category']
Se va distribui fiecare valoare categorică pe un grafic in raport cu variabila target.
for col in categorical_variables:
fig, ax = plt.subplots(figsize = (8,7))
ax = sns.countplot(data = data, x = col, hue = 'Attrition_Flag')
plt.show()
Observații: Din punct de vedere al sexului, categoria cu cei mai mulți clienți o reprezintă persoanele de sex feminin. În cazul nivelului de educație, cei mai mulți clienți au absolvit facultatea. Având în vedere starea civilă, cei mai mulți clienți sunt căsătoriți. În ceea ce privește categoria de venit, cei mai mulți clienți au un salariu mai mic de $40K anual. În ceea ce privește categoria de card, cei mai mulți clienți au optat pentru varianta "Blue".
b) Verificăm valorile lipsă din cadrul variabilelor categorice
data[categorical_variables].isnull().sum()
Gender 0 Education_Level 1519 Marital_Status 749 Income_Category 2 Card_Category 0 dtype: int64
Observăm că pentru nivelul de educație nu avem date pentru 1519 clienți, pentru starea civilă datele nu sunt disponibile pentru 749 de clienți, iar datele referitoare la categoria de venit lipsesc pentru 2 clienți.
msno.bar(data[categorical_variables])
<Axes: >
Acest grafic ne ajută să vizualizăm valorile lipsă din cadrul variabilelor.
data[categorical_variables].value_counts()
Gender Education_Level Marital_Status Income_Category Card_Category
F Graduate Married Less than $40K Blue 467
Single Less than $40K Blue 382
High School Married Less than $40K Blue 280
Single Less than $40K Blue 228
M Graduate Married $80K - $120K Blue 221
...
F Post-Graduate Married $40K - $60K Silver 1
Single $40K - $60K Silver 1
Less than $40K Silver 1
abc Gold 1
M Post-Graduate Divorced $40K - $60K Blue 1
Length: 317, dtype: int64
Completam datele necunoscute
data['Education_Level'] = data['Education_Level'].fillna('Unknown')
data['Marital_Status'] = data['Marital_Status'].fillna('Unknown')
data['Income_Category'] = data['Income_Category'].fillna('Unknown')
data[categorical_variables].isnull()
| Gender | Education_Level | Marital_Status | Income_Category | Card_Category | |
|---|---|---|---|---|---|
| 0 | False | False | False | False | False |
| 1 | False | False | False | False | False |
| 2 | False | False | False | False | False |
| 3 | False | False | False | False | False |
| 4 | False | False | False | False | False |
| ... | ... | ... | ... | ... | ... |
| 10122 | False | False | False | False | False |
| 10123 | False | False | False | False | False |
| 10124 | False | False | False | False | False |
| 10125 | False | False | False | False | False |
| 10126 | False | False | False | False | False |
10127 rows × 5 columns
data[categorical_variables].isnull().sum()
Gender 0 Education_Level 0 Marital_Status 0 Income_Category 0 Card_Category 0 dtype: int64
c) Variația valorilor categorice
data[categorical_variables].nunique()
Gender 2 Education_Level 7 Marital_Status 4 Income_Category 7 Card_Category 4 dtype: int64
Observăm că nu există variabile cu o singură valoare, așadar nu modificăm categorical_variables.
d) Convertirea variabilelor
data['Gender'].value_counts()/len(data)*100
F 52.908068 M 47.091932 Name: Gender, dtype: float64
data['Education_Level'].value_counts()/len(data)*100
Graduate 30.887726 High School 19.877555 Unknown 14.999506 Uneducated 14.683519 College 10.002962 Post-Graduate 5.095290 Doctorate 4.453441 Name: Education_Level, dtype: float64
data['Marital_Status'].value_counts()/len(data)*100
Married 46.282216 Single 38.935519 Unknown 7.396070 Divorced 7.386195 Name: Marital_Status, dtype: float64
data['Income_Category'].value_counts()/len(data)*100
Less than $40K 35.153550 $40K - $60K 17.665646 $80K - $120K 15.157500 $60K - $80K 13.844179 abc 10.980547 $120K + 7.178829 Unknown 0.019749 Name: Income_Category, dtype: float64
data['Card_Category'].value_counts()/len(data)*100
Blue 93.176656 Silver 5.480399 Gold 1.145453 Platinum 0.197492 Name: Card_Category, dtype: float64
Observăm că în ceea ce privește coloana "Income_Category", există o ramură de venit denumită "abc" care nu ne este utilă și pe care o putem muta într-un alt loc, împreună cu categoria "unknown".
Folosim encoding și adăugăm valoarea 'abc' din variabila 'Income_Category' în 'Unknown'.
data['Income_Category'] = np.where(data['Income_Category'] == 'abc','Unknown',data['Income_Category'])
data['Income_Category'].value_counts()/len(data)*100
Less than $40K 35.153550 $40K - $60K 17.665646 $80K - $120K 15.157500 $60K - $80K 13.844179 Unknown 11.000296 $120K + 7.178829 Name: Income_Category, dtype: float64
data[categorical_variables].nunique()
Gender 2 Education_Level 7 Marital_Status 4 Income_Category 6 Card_Category 4 dtype: int64
e) Explorarea variabilelor numerice
numerical_columns = [col for col in data.columns if data[col].dtypes != 'object']
print('Sunt',len(numerical_columns),'coloane numerice. Acestea sunt',numerical_columns)
Sunt 16 coloane numerice. Acestea sunt ['CLIENTNUM', 'Attrition_Flag', 'Customer_Age', 'Dependent_count', 'Months_on_book', 'Total_Relationship_Count', 'Months_Inactive_12_mon', 'Contacts_Count_12_mon', 'Credit_Limit', 'Total_Used_Bal', 'Total_Unused_Bal', 'Total_Amt_Chng_Q4_Q1', 'Total_Trans_Amt', 'Total_Trans_Ct', 'Total_Ct_Chng_Q4_Q1', 'Avg_Utilization_Ratio']
data[numerical_columns].head()
| CLIENTNUM | Attrition_Flag | Customer_Age | Dependent_count | Months_on_book | Total_Relationship_Count | Months_Inactive_12_mon | Contacts_Count_12_mon | Credit_Limit | Total_Used_Bal | Total_Unused_Bal | Total_Amt_Chng_Q4_Q1 | Total_Trans_Amt | Total_Trans_Ct | Total_Ct_Chng_Q4_Q1 | Avg_Utilization_Ratio | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 768805383 | 1 | 45.0 | 3.0 | 39 | 5 | 1 | 3 | 12691.0 | 777 | 11914.0 | 1.335 | 1144 | 42 | 1.625 | 0.061 |
| 1 | 818770008 | 1 | 49.0 | 5.0 | 44 | 6 | 1 | 2 | 8256.0 | 864 | 7392.0 | 1.541 | 1291 | 33 | 3.714 | 0.105 |
| 2 | 713982108 | 1 | 51.0 | 3.0 | 36 | 4 | 1 | 0 | 3418.0 | 0 | 3418.0 | 2.594 | 1887 | 20 | 2.333 | 0.000 |
| 3 | 769911858 | 1 | 40.0 | 4.0 | 34 | 3 | 4 | 1 | 3313.0 | 2517 | 796.0 | 1.405 | 1171 | 20 | 2.333 | 0.760 |
| 4 | 709106358 | 1 | 40.0 | 3.0 | 21 | 5 | 1 | 0 | 4716.0 | 0 | 4716.0 | 2.175 | 816 | 28 | 2.500 | 0.000 |
Grafice
for col in numerical_columns:
fig, ax = plt.subplots(figsize = (15,6))
sns.histplot(data = data, x = col, hue = 'Attrition_Flag',bins = 100)
plt.show()
data[numerical_columns].isnull().sum()
CLIENTNUM 0 Attrition_Flag 0 Customer_Age 3 Dependent_count 5 Months_on_book 0 Total_Relationship_Count 0 Months_Inactive_12_mon 0 Contacts_Count_12_mon 0 Credit_Limit 0 Total_Used_Bal 0 Total_Unused_Bal 0 Total_Amt_Chng_Q4_Q1 0 Total_Trans_Amt 0 Total_Trans_Ct 0 Total_Ct_Chng_Q4_Q1 0 Avg_Utilization_Ratio 0 dtype: int64
msno.bar(data[numerical_columns])
<Axes: >
Prin acest grafic observăm că lipsesc 5 valori din Dependent_count și 3 valori din Customer_Age
data['Dependent_count']
0 3.0
1 5.0
2 3.0
3 4.0
4 3.0
...
10122 2.0
10123 2.0
10124 1.0
10125 2.0
10126 2.0
Name: Dependent_count, Length: 10127, dtype: float64
data['Dependent_count'] = data['Dependent_count'].fillna(data['Dependent_count'].min())
data['Customer_Age']
0 45.0
1 49.0
2 51.0
3 40.0
4 40.0
...
10122 50.0
10123 41.0
10124 44.0
10125 30.0
10126 43.0
Name: Customer_Age, Length: 10127, dtype: float64
data['Customer_Age'] = data['Customer_Age'].fillna(data['Customer_Age'].mean())
data['Dependent_count'].isnull().sum()
data['Customer_Age'].isnull().sum()
0
Acum putem observa că nu mai există valori nule in variabilele numerice.
f) Căutăm dacă avem valori obligatorii unice pe care le putem elimina.
data[numerical_columns].nunique()
CLIENTNUM 10127 Attrition_Flag 2 Customer_Age 46 Dependent_count 6 Months_on_book 44 Total_Relationship_Count 6 Months_Inactive_12_mon 7 Contacts_Count_12_mon 7 Credit_Limit 6205 Total_Used_Bal 1974 Total_Unused_Bal 6813 Total_Amt_Chng_Q4_Q1 1158 Total_Trans_Amt 5033 Total_Trans_Ct 126 Total_Ct_Chng_Q4_Q1 830 Avg_Utilization_Ratio 964 dtype: int64
Având în vedere că nu există astfel de valori, nu vom modifica tabelul final.
data['Credit_Limit'].value_counts()/len(data)*100
34516.0 5.016293
1438.3 5.006418
9959.0 0.177743
15987.0 0.177743
23981.0 0.118495
...
9183.0 0.009875
29923.0 0.009875
9551.0 0.009875
11558.0 0.009875
10388.0 0.009875
Name: Credit_Limit, Length: 6205, dtype: float64
g) Detectam valorile extreme (outliers)
q1 = data['Credit_Limit'].quantile(0.25)
q3 = data['Credit_Limit'].quantile(0.75)
IQR = q3 - q1
lower_limit = q1 - 3 * IQR
upper_limit = q3 + 3 * IQR
data[(data['Credit_Limit']<lower_limit) | (data['Credit_Limit']>upper_limit)]['Credit_Limit']
Series([], Name: Credit_Limit, dtype: float64)
data[(data['Credit_Limit']<lower_limit) | (data['Credit_Limit']>upper_limit)]
| CLIENTNUM | Attrition_Flag | Customer_Age | Gender | Dependent_count | Education_Level | Marital_Status | Income_Category | Card_Category | Months_on_book | ... | Months_Inactive_12_mon | Contacts_Count_12_mon | Credit_Limit | Total_Used_Bal | Total_Unused_Bal | Total_Amt_Chng_Q4_Q1 | Total_Trans_Amt | Total_Trans_Ct | Total_Ct_Chng_Q4_Q1 | Avg_Utilization_Ratio |
|---|
0 rows × 21 columns
fig, ax = plt.subplots(figsize = (8,6))
sns.boxplot(y = data['Credit_Limit'], color = '#50C878', whis = 3)
plt.show()
În acest caz nu avem outlier-i, deci vom căuta și în celelalte variabile.
for col in numerical_columns:
fig, ax = plt.subplots(figsize = (8,6))
sns.boxplot(y = data[col], color = '#7F00FF', whis = 3)
plt.show()
Observăm că următoarele variabile sunt afectate de outlier-i: Total_Ct_Chng_Q4_Q1, Total_Trans_Amt, Total_Amt_Chng_Q4_Q1, Attrition_Flag. Vom înlocui valorile outlier cu limita inferioară, atunci când acestea sunt mai mici decât limita, sau cu limita superioară, dacă valorile sunt mai mari decât aceasta.
affected_by_outliers = ['Total_Ct_Chng_Q4_Q1','Total_Trans_Amt','Total_Amt_Chng_Q4_Q1','Attrition_Flag']
def censoring_outliers(dataframe,column):
q1 = dataframe[column].quantile(0.25)
q3 = dataframe[column].quantile(0.75)
IQR = q3-q1
lower_limit = q1 - 3 * IQR
upper_limit = q3 + 3 * IQR
dataframe[column] = np.where(dataframe[column] < lower_limit, lower_limit, np.where(dataframe[column] > upper_limit, upper_limit, dataframe[column]))
for variable in affected_by_outliers:
censoring_outliers(data,variable)
for col in numerical_columns:
fig, ax = plt.subplots(figsize = (8,6))
sns.boxplot(y = data[col], color = 'pink', whis = 3)
plt.show()
h) Corelatie
correlation = data[numerical_columns].corr()
correlation
| CLIENTNUM | Attrition_Flag | Customer_Age | Dependent_count | Months_on_book | Total_Relationship_Count | Months_Inactive_12_mon | Contacts_Count_12_mon | Credit_Limit | Total_Used_Bal | Total_Unused_Bal | Total_Amt_Chng_Q4_Q1 | Total_Trans_Amt | Total_Trans_Ct | Total_Ct_Chng_Q4_Q1 | Avg_Utilization_Ratio | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| CLIENTNUM | 1.000000 | NaN | 0.007561 | 0.007280 | 0.134588 | 0.006907 | 0.005729 | 0.005694 | 0.005708 | 0.000825 | 0.005633 | 0.019504 | -0.020926 | -0.002961 | 0.007672 | 0.000266 |
| Attrition_Flag | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| Customer_Age | 0.007561 | NaN | 1.000000 | -0.122720 | 0.788890 | -0.010925 | 0.054321 | -0.018468 | 0.002561 | 0.014733 | 0.001239 | -0.068964 | -0.046050 | -0.067107 | -0.018824 | 0.007023 |
| Dependent_count | 0.007280 | NaN | -0.122720 | 1.000000 | -0.103872 | -0.039495 | -0.012156 | -0.040394 | 0.068422 | -0.003003 | 0.068676 | -0.036795 | 0.033607 | 0.052085 | 0.010959 | -0.037256 |
| Months_on_book | 0.134588 | NaN | 0.788890 | -0.103872 | 1.000000 | -0.009203 | 0.074164 | -0.010774 | 0.007507 | 0.008623 | 0.006732 | -0.055050 | -0.037315 | -0.049819 | -0.018958 | -0.007541 |
| Total_Relationship_Count | 0.006907 | NaN | -0.010925 | -0.039495 | -0.009203 | 1.000000 | -0.003675 | 0.055203 | -0.071386 | 0.013726 | -0.072601 | 0.049542 | -0.359299 | -0.241891 | 0.041464 | 0.067663 |
| Months_Inactive_12_mon | 0.005729 | NaN | 0.054321 | -0.012156 | 0.074164 | -0.003675 | 1.000000 | 0.029493 | -0.020394 | -0.042210 | -0.016605 | -0.032146 | -0.036493 | -0.042787 | -0.043911 | -0.007503 |
| Contacts_Count_12_mon | 0.005694 | NaN | -0.018468 | -0.040394 | -0.010774 | 0.055203 | 0.029493 | 1.000000 | 0.020817 | -0.053913 | 0.025646 | -0.019470 | -0.122814 | -0.152213 | -0.098280 | -0.055471 |
| Credit_Limit | 0.005708 | NaN | 0.002561 | 0.068422 | 0.007507 | -0.071386 | -0.020394 | 0.020817 | 1.000000 | 0.042493 | 0.995981 | 0.012631 | 0.169344 | 0.075927 | -0.006200 | -0.482965 |
| Total_Used_Bal | 0.000825 | NaN | 0.014733 | -0.003003 | 0.008623 | 0.013726 | -0.042210 | -0.053913 | 0.042493 | 1.000000 | -0.047167 | 0.057863 | 0.059969 | 0.056060 | 0.095009 | 0.624022 |
| Total_Unused_Bal | 0.005633 | NaN | 0.001239 | 0.068676 | 0.006732 | -0.072601 | -0.016605 | 0.025646 | 0.995981 | -0.047167 | 1.000000 | 0.007441 | 0.163932 | 0.070885 | -0.014717 | -0.538808 |
| Total_Amt_Chng_Q4_Q1 | 0.019504 | NaN | -0.068964 | -0.036795 | -0.055050 | 0.049542 | -0.032146 | -0.019470 | 0.012631 | 0.057863 | 0.007441 | 1.000000 | 0.055264 | 0.024123 | 0.366869 | 0.036923 |
| Total_Trans_Amt | -0.020926 | NaN | -0.046050 | 0.033607 | -0.037315 | -0.359299 | -0.036493 | -0.122814 | 0.169344 | 0.059969 | 0.163932 | 0.055264 | 1.000000 | 0.832520 | 0.117409 | -0.078081 |
| Total_Trans_Ct | -0.002961 | NaN | -0.067107 | 0.052085 | -0.049819 | -0.241891 | -0.042787 | -0.152213 | 0.075927 | 0.056060 | 0.070885 | 0.024123 | 0.832520 | 1.000000 | 0.152392 | 0.002838 |
| Total_Ct_Chng_Q4_Q1 | 0.007672 | NaN | -0.018824 | 0.010959 | -0.018958 | 0.041464 | -0.043911 | -0.098280 | -0.006200 | 0.095009 | -0.014717 | 0.366869 | 0.117409 | 0.152392 | 1.000000 | 0.080625 |
| Avg_Utilization_Ratio | 0.000266 | NaN | 0.007023 | -0.037256 | -0.007541 | 0.067663 | -0.007503 | -0.055471 | -0.482965 | 0.624022 | -0.538808 | 0.036923 | -0.078081 | 0.002838 | 0.080625 | 1.000000 |
fig, ax = plt.subplots(figsize = (24,10))
sns.heatmap(correlation, annot = True, square = True, fmt = '.2f')
plt.show()
Observăm o puternică corelație pozitivă de de 0,83 între Total_Trans_Ct și Total_Trans_Amt. De asemenea, există o puternică corelație de 0,79 între Months_On_Book și Customer_Age. În plus, există o corelație medie pozitivă de 0,62 între Total_Used_Bal și Avg_Utilization_Ratio.
În ceea ce privește corelațiile slabe, putem observa o corelație pozitivă de 0,37 între Total_Trans_Ct și Attrition_Flag, precum și între Total_Ct_Chng_Q4_Q1 și Total_Amt_Chng_Q4_Q1. Există, de asemenea, o corelație slabă de 0,31 între Total_Ct_Chng_Q4_Q1 și Attrition_Flag.
Pe de altă parte, există o corelație slabă negativă de -0,36 între Total_Trans_Amt și Total_Relationship_Count. De asemenea, putem observa o corelație negativă slabă de -0,48 între Credit_Limit și Avg_Utilization_Ratio. Există, de asemenea, o corelație negativă medie de -0,54 între Avg_Utilization_Ratio și Total_Unused_Bal.
Celelalte corelații au un impact scăzut, ceea ce înseamnă că au o relevanță mult mai mică.
#variabila target
y = data['Attrition_Flag']
#variabila independenta
X = data.drop(columns = ['Attrition_Flag'])
X.shape, y.shape
((10127, 20), (10127,))
În cazul modelului nostru, categoriile cu cea mai mică relevanță sunt Gender și Card_Category, așadar le vom elimina.
X=X.drop(columns=['Gender','Card_Category'])
X.head()
| CLIENTNUM | Customer_Age | Dependent_count | Education_Level | Marital_Status | Income_Category | Months_on_book | Total_Relationship_Count | Months_Inactive_12_mon | Contacts_Count_12_mon | Credit_Limit | Total_Used_Bal | Total_Unused_Bal | Total_Amt_Chng_Q4_Q1 | Total_Trans_Amt | Total_Trans_Ct | Total_Ct_Chng_Q4_Q1 | Avg_Utilization_Ratio | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 768805383 | 45.0 | 3.0 | High School | Married | $60K - $80K | 39 | 5 | 1 | 3 | 12691.0 | 777 | 11914.0 | 1.335 | 1144.0 | 42 | 1.526 | 0.061 |
| 1 | 818770008 | 49.0 | 5.0 | Graduate | Single | Less than $40K | 44 | 6 | 1 | 2 | 8256.0 | 864 | 7392.0 | 1.541 | 1291.0 | 33 | 1.526 | 0.105 |
| 2 | 713982108 | 51.0 | 3.0 | Graduate | Married | $80K - $120K | 36 | 4 | 1 | 0 | 3418.0 | 0 | 3418.0 | 1.543 | 1887.0 | 20 | 1.526 | 0.000 |
| 3 | 769911858 | 40.0 | 4.0 | High School | Unknown | Less than $40K | 34 | 3 | 4 | 1 | 3313.0 | 2517 | 796.0 | 1.405 | 1171.0 | 20 | 1.526 | 0.760 |
| 4 | 709106358 | 40.0 | 3.0 | Uneducated | Married | $60K - $80K | 21 | 5 | 1 | 0 | 4716.0 | 0 | 4716.0 | 1.543 | 816.0 | 28 | 1.526 | 0.000 |
c) Categorical coding
categorical_columns = [col for col in X.columns if X[col].dtypes == 'object']
categorical_columns
['Education_Level', 'Marital_Status', 'Income_Category']
X = pd.get_dummies(X,columns = categorical_columns)
X.head()
| CLIENTNUM | Customer_Age | Dependent_count | Months_on_book | Total_Relationship_Count | Months_Inactive_12_mon | Contacts_Count_12_mon | Credit_Limit | Total_Used_Bal | Total_Unused_Bal | ... | Marital_Status_Divorced | Marital_Status_Married | Marital_Status_Single | Marital_Status_Unknown | Income_Category_$120K + | Income_Category_$40K - $60K | Income_Category_$60K - $80K | Income_Category_$80K - $120K | Income_Category_Less than $40K | Income_Category_Unknown | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 768805383 | 45.0 | 3.0 | 39 | 5 | 1 | 3 | 12691.0 | 777 | 11914.0 | ... | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
| 1 | 818770008 | 49.0 | 5.0 | 44 | 6 | 1 | 2 | 8256.0 | 864 | 7392.0 | ... | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
| 2 | 713982108 | 51.0 | 3.0 | 36 | 4 | 1 | 0 | 3418.0 | 0 | 3418.0 | ... | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
| 3 | 769911858 | 40.0 | 4.0 | 34 | 3 | 4 | 1 | 3313.0 | 2517 | 796.0 | ... | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 |
| 4 | 709106358 | 40.0 | 3.0 | 21 | 5 | 1 | 0 | 4716.0 | 0 | 4716.0 | ... | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
5 rows × 32 columns
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=10)
X_train.shape, X_test.shape, y_train.shape, y_test.shape
((8101, 32), (2026, 32), (8101,), (2026,))
rf = RandomForestClassifier(n_estimators = 300, max_depth = 5, n_jobs = -1, random_state = 123)
Începem procesul de antrenare a modelului cu ajutorul algoritmului Random Forest pe setul de date pregătit anterior.
rf.fit(X_train, y_train)
RandomForestClassifier(max_depth=5, n_estimators=300, n_jobs=-1,
random_state=123)In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. RandomForestClassifier(max_depth=5, n_estimators=300, n_jobs=-1,
random_state=123)y_predict = rf.predict(X_test)
y_predict
array([1, 1, 1, ..., 0, 1, 1], dtype=int64)
În urma aplicării algoritmului Random Forest, verificăm criteriile de performanță ale modelului nostru. Acest lucru include evaluarea acurateții, preciziei, rapelului și scorului F1. De asemenea, putem examina matricea de confuzie pentru a evalua cât de bine modelul nostru poate distinge între clasele pozitive și negative.
a) Acuratețea
accuracy = accuracy_score(y_test,y_predict)
print('Pentru algoritmul Random Forest Classifier, avem o acuratete de:',accuracy)
Pentru algoritmul Random Forest Classifier, avem o acuratete de: 0.920533070088845
b) Matricea de confuzie
cm = confusion_matrix(y_test,y_predict)
print(cm)
[[ 171 150] [ 11 1694]]
fig,ax = plt.subplots(figsize = (10, 8))
sns.heatmap(cm,annot = True, fmt = 'd')
plt.show()
Prin analizarea graficului furnizat, putem concluziona că există 1694 valori cu adevărat pozitive și 171 valori cu adevărat negative, dar și 11 valori eronat considerate negative și 150 valori eronat considerate pozitive. În urma acestor constatări, precizia rezultată este de 91,8%, sensibilitatea este de 99,3%, scorul AUC este de 76,3%, iar acuratețea globală este de 92%.
c) Precizie și sensibilitate
precision = precision_score(y_test, y_predict)
recall = recall_score(y_test, y_predict)
print('Precision score:', precision)
print('Recall score:', recall)
Precision score: 0.9186550976138829 Recall score: 0.9935483870967742
Verificam daca exista overfitting
y_train_predict = rf.predict(X_train)
precision = precision_score(y_train, y_train_predict)
recall = recall_score(y_train, y_train_predict)
print('Precision score:', precision)
print('Recall score:', recall)
Precision score: 0.9142003801248981 Recall score: 0.9910228108903606
Interpretarea datelor indică că valorile preciziei și sensibilității s-au modificat într-o măsură neglijabilă. Precizia a scăzut cu aproximativ 0,004, iar sensibilitatea a scăzut cu aproximativ 0,002. Acest lucru sugerează că nu există un overfitting semnificativ care să afecteze rezultatele obținute.
d) Scorul AUC
auc_score = roc_auc_score(y_test, y_predict)
print('AUC score is:', auc_score)
AUC score is: 0.763129333735303
Verificam daca exista overfitting
y_predict_train = rf.predict(X_train)
auc_score = roc_auc_score(y_train, y_predict_train)
print('AUC score is:', auc_score)
AUC score is: 0.7535512216779522
Diferența dintre setul de testare și cel de antrenare este prea mică pentru a avea un overfitting semnificativ care să influențeze rezultatele.
fpr,tpr,treshold = roc_curve(y_test, y_predict)
plt.plot(fpr,tpr)
[<matplotlib.lines.Line2D at 0x18c596a7220>]
Conform interpretării graficului, acesta ilustrează o curbă ROC ce reflectă performanța unui model de clasificare. Se poate observa că între 0.0 și 0.5, curba crește într-un mod specific funcției f(x) = 2*x, iar între 0.5 și 1, valoarea curbei rămâne constantă la 1.
xgb = XGBClassifier(n_estimators=250, max_depth=4, n_jobs=-1, random_state=100)
xgb.fit(X_train, y_train)
XGBClassifier(base_score=None, booster=None, callbacks=None,
colsample_bylevel=None, colsample_bynode=None,
colsample_bytree=None, early_stopping_rounds=None,
enable_categorical=False, eval_metric=None, feature_types=None,
gamma=None, gpu_id=None, grow_policy=None, importance_type=None,
interaction_constraints=None, learning_rate=None, max_bin=None,
max_cat_threshold=None, max_cat_to_onehot=None,
max_delta_step=None, max_depth=4, max_leaves=None,
min_child_weight=None, missing=nan, monotone_constraints=None,
n_estimators=250, n_jobs=-1, num_parallel_tree=None,
predictor=None, random_state=100, ...)In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. XGBClassifier(base_score=None, booster=None, callbacks=None,
colsample_bylevel=None, colsample_bynode=None,
colsample_bytree=None, early_stopping_rounds=None,
enable_categorical=False, eval_metric=None, feature_types=None,
gamma=None, gpu_id=None, grow_policy=None, importance_type=None,
interaction_constraints=None, learning_rate=None, max_bin=None,
max_cat_threshold=None, max_cat_to_onehot=None,
max_delta_step=None, max_depth=4, max_leaves=None,
min_child_weight=None, missing=nan, monotone_constraints=None,
n_estimators=250, n_jobs=-1, num_parallel_tree=None,
predictor=None, random_state=100, ...)y_predict = xgb.predict(X_test)
y_predict
array([1, 1, 1, ..., 0, 1, 1])
a) Acuratețea
accuracy = accuracy_score(y_test, y_predict)
print('Accuracy for XGBoost is:', accuracy)
Accuracy for XGBoost is: 0.9693978282329714
b) Matricea de confuzie
cm = confusion_matrix(y_test, y_predict)
print(cm)
[[ 280 41] [ 21 1684]]
fig, ax = plt.subplots(figsize = (11, 8))
sns.heatmap(cm, annot = True, fmt='d')
plt.show()
Bazându-ne pe analiza și graficul furnizate, observăm că avem 1684 valori cu adevărat pozitive și 280 valori cu adevărat negative, împreună cu 21 de valori fals negative și 41 valori fals pozitive. Prin urmare, putem concluziona că acuratețea este de 96,9%, precizia este de 97,6%, sensibilitatea este de 98,7%, iar scorul AUC este de 92,9%.
c) Precizie și sensibilitate
precision = precision_score(y_test, y_predict)
recall = recall_score(y_test, y_predict)
print('Precision score:', precision)
print('Recall score:', recall)
Precision score: 0.976231884057971 Recall score: 0.987683284457478
Verificam daca exista overfitting
y_train_predict = rf.predict(X_train)
precision = precision_score(y_train, y_train_predict)
recall = recall_score(y_train, y_train_predict)
print('Precision score:', precision)
print('Recall score:', recall)
Precision score: 0.9142003801248981 Recall score: 0.9910228108903606
Scorul de sensibilitate s-a modificat cu o valoare neglijabilă de aproximativ 0,035, sugestiv pentru absența unui overfitting sau underfitting relevant. În schimb, scorul de precizie a scăzut cu o valoare semnificativă de peste 0,6, ceea ce sugerează existența unui underfitting important.
d) Scorul AUC
auc_score = roc_auc_score(y_test, y_predict)
print('AUC score', auc_score)
AUC score 0.9299787138798294
Verificam daca exista overfitting
y_predict_train = xgb.predict(X_train)
auc_score = roc_auc_score(y_train, y_predict_train)
print('AUC score is:', auc_score)
AUC score is: 1.0
Se pare că există un overfitting în ceea ce privește scorul AUC, având în vedere că diferența dintre rezultatele de la antrenare și cele de la testare este de aproximativ 0,7. Scorul AUC obținut la antrenare a atins chiar valoarea maximă de 1, ceea ce sugerează o posibilă adaptare excesivă a modelului la datele de antrenare.
a) Vom identifica acum posibile hipervalori pentru algoritmul XGBoost.
n_estimators = [150,250]
max_depth = [3,4]
learning_rate = [0.1,0.05]
b) Cautam hipervalorile
results = []
for est in n_estimators:
for md in max_depth:
for lr in learning_rate:
xgb = XGBClassifier(n_estimators = est, max_depth = md, learning_rate = lr, n_jobs = -1, random_state = 100, subsample = 0.7, colsample_bytree = 0.5)
xgb.fit(X_train, y_train)
y_predict = xgb.predict(X_test)
auc_score = roc_auc_score(y_test,y_predict)
results.append(['estimators', est, 'max_depth', md, 'leraning_rate', lr, 'auc', auc_score])
print(results)
[['estimators', 200, 'max_depth', 3, 'leraning_rate', 0.1, 'auc', 0.9262769388183918], ['estimators', 200, 'max_depth', 3, 'leraning_rate', 0.05, 'auc', 0.91498890015622], ['estimators', 200, 'max_depth', 4, 'leraning_rate', 0.1, 'auc', 0.930565224143759], ['estimators', 200, 'max_depth', 4, 'leraning_rate', 0.05, 'auc', 0.9138158796283607], ['estimators', 300, 'max_depth', 3, 'leraning_rate', 0.1, 'auc', 0.9268634490823215], ['estimators', 300, 'max_depth', 3, 'leraning_rate', 0.05, 'auc', 0.9146956450242553], ['estimators', 300, 'max_depth', 4, 'leraning_rate', 0.1, 'auc', 0.9284210814810755], ['estimators', 300, 'max_depth', 4, 'leraning_rate', 0.05, 'auc', 0.9196617973524817]]
results
[['estimators', 200, 'max_depth', 3, 'leraning_rate', 0.1, 'auc', 0.9262769388183918], ['estimators', 200, 'max_depth', 3, 'leraning_rate', 0.05, 'auc', 0.91498890015622], ['estimators', 200, 'max_depth', 4, 'leraning_rate', 0.1, 'auc', 0.930565224143759], ['estimators', 200, 'max_depth', 4, 'leraning_rate', 0.05, 'auc', 0.9138158796283607], ['estimators', 300, 'max_depth', 3, 'leraning_rate', 0.1, 'auc', 0.9268634490823215], ['estimators', 300, 'max_depth', 3, 'leraning_rate', 0.05, 'auc', 0.9146956450242553], ['estimators', 300, 'max_depth', 4, 'leraning_rate', 0.1, 'auc', 0.9284210814810755], ['estimators', 300, 'max_depth', 4, 'leraning_rate', 0.05, 'auc', 0.9196617973524817]]
Modelul cu parametrii n_estimators=200, max_depth=4 și learning_rate=0.1 este cel mai performant.
Verificam daca exista overfitting
best_model = XGBClassifier(n_estimator = 200, max_depth = 4, rate = 0.1, n_jobs = -1, subsample = 0.6, solsample_bytree = 0.5, random_state = 123)
best_model.fit(X_train, y_train)
y_predict_train = best_model.predict(X_train)
y_predict_test = best_model.predict(X_test)
auc_score_train = roc_auc_score(y_train, y_predict_train)
auc_score_test = roc_auc_score(y_test, y_predict_test)
print('AUC train', auc_score_train)
print('AUC test', auc_score_test)
[14:24:41] WARNING: C:\buildkite-agent\builds\buildkite-windows-cpu-autoscaling-group-i-07593ffd91cd9da33-1\xgboost\xgboost-ci-windows\src\learner.cc:767:
Parameters: { "n_estimator", "rate", "solsample_bytree" } are not used.
AUC train 0.9941986214077326
AUC test 0.9175176546897982
Diferența dintre scorul AUC obținut pentru setul de antrenare și cel obținut pentru setul de testare este de aproximativ 0,079, ceea ce indică existența unui overfitting.
with open('C:\\Users\\valen\\Desktop\\data\\final_model.pkl','wb') as file:
pickle.dump(best_model, file)
Scopul acestei analize este de a evalua eficacitatea unui model predictiv.
y_predict_proba = xgb.predict_proba(X_test)
y_predict_proba
array([[3.4443736e-02, 9.6555626e-01],
[3.8262010e-03, 9.9617380e-01],
[2.5113821e-03, 9.9748862e-01],
...,
[9.8464829e-01, 1.5351691e-02],
[9.6988678e-04, 9.9903011e-01],
[4.4490695e-02, 9.5550931e-01]], dtype=float32)
a) Calculăm probabilitățile.
y_predict_proba_class_1 = y_predict_proba[:, 1]
y_predict_proba_class_1
array([0.96555626, 0.9961738 , 0.9974886 , ..., 0.01535169, 0.9990301 ,
0.9555093 ], dtype=float32)
b) Scorul AUC
auc_score = roc_auc_score(y_test, y_predict_proba_class_1)
print(auc_score)
0.9931263189629183
# vom crea un nou DataFrame gol
lift_gain_report = pd.DataFrame()
#il vom introduce pe y_test in DataFrame
lift_gain_report['y_test'] = y_test
#vom adăuga probabilitățile în DataFrame
lift_gain_report['Predicted Probabilities'] = y_predict_proba_class_1
#vom ordona probabilitățile descrescător
lift_gain_report['Probabilities Rank'] = lift_gain_report['Predicted Probabilities'].rank(method='first',ascending=False,pct=True)
#vom calcula o coloană de decili pentru a separa observațiile
lift_gain_report['Decil group']=np.floor((1- lift_gain_report['Probabilities Rank'])*10)+1
#grupăm observațiile
lift_gain_report['No of observations']=1
lift_gain_report=lift_gain_report.groupby(['Decil group']).sum().reset_index()
#introducem numărul cumulativ de observații
lift_gain_report['Cumulative no of observations']=lift_gain_report['No of observations'].cumsum()
#procentul cumulativ
lift_gain_report['Cumulative % of observations']=lift_gain_report['Cumulative no of observations']/lift_gain_report['Cumulative no of observations'].max()
#vom calcula cumulativele pozitive
lift_gain_report['Cumulative no of positives']=lift_gain_report['y_test'].cumsum()
#vom calcula cumulativele pozitive(Gain)
lift_gain_report['Gain']=lift_gain_report['Cumulative no of positives']/lift_gain_report['Cumulative no of positives'].max()
#vom calcula valorile Lift
lift_gain_report['Lift']=lift_gain_report['Gain']/lift_gain_report['Cumulative % of observations']
lift_gain_report
| Decil group | y_test | Predicted Probabilities | Probabilities Rank | No of observations | Cumulative no of observations | Cumulative % of observations | Cumulative no of positives | Gain | Lift | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1.0 | 3 | 6.797051 | 192.880059 | 203 | 203 | 0.100197 | 3 | 0.001760 | 0.017561 |
| 1 | 2.0 | 93 | 107.519356 | 172.539980 | 203 | 406 | 0.200395 | 96 | 0.056305 | 0.280970 |
| 2 | 3.0 | 191 | 188.822098 | 151.500000 | 202 | 608 | 0.300099 | 287 | 0.168328 | 0.560910 |
| 3 | 4.0 | 203 | 199.250183 | 131.960020 | 203 | 811 | 0.400296 | 490 | 0.287390 | 0.717944 |
| 4 | 5.0 | 202 | 200.446136 | 111.119941 | 202 | 1013 | 0.500000 | 692 | 0.405865 | 0.811730 |
| 5 | 6.0 | 203 | 202.192703 | 91.380059 | 203 | 1216 | 0.600197 | 895 | 0.524927 | 0.874590 |
| 6 | 7.0 | 203 | 202.560715 | 71.039980 | 203 | 1419 | 0.700395 | 1098 | 0.643988 | 0.919465 |
| 7 | 8.0 | 202 | 201.749695 | 50.500000 | 202 | 1621 | 0.800099 | 1300 | 0.762463 | 0.952962 |
| 8 | 9.0 | 203 | 202.841263 | 30.460020 | 203 | 1824 | 0.900296 | 1503 | 0.881525 | 0.979150 |
| 9 | 10.0 | 202 | 201.917343 | 10.119941 | 202 | 2026 | 1.000000 | 1705 | 1.000000 | 1.000000 |
a) Graficul Lift
fig,ax=plt.subplots(figsize=(15,9))
barplot=plt.bar(lift_gain_report['Decil group'],lift_gain_report['Lift'])
plt.title('Lift bar plot')
plt.xlabel('Decil group')
plt.ylabel('Lift')
plt.xticks(lift_gain_report['Decil group'])
for b in barplot:
plt.text(b.get_x()+b.get_width()/2,b.get_height()+0.1,round(b.get_height(),2),ha='center')
plt.show()
Din analiza graficului pentru indicatorul Lift, se poate observa că pentru 7 din cele 10 grupuri Decile, valoarea indicatorului este de 1,19. Pentru grupul Decil 8, valoarea Lift este de 1,18, pentru Decil 9 este de 1,11, iar pentru Decil 10 este de 1.
b) Graficul Gain
lift_gain_report['Random Selection']=lift_gain_report['Decil group']/lift_gain_report['Decil group'].max()
fig,ax=plt.subplots(figsize=(14,8))
sns.lineplot(data=lift_gain_report,x=lift_gain_report['Decil group'],y=lift_gain_report['Gain'])
sns.lineplot(data=lift_gain_report,x=lift_gain_report['Decil group'],y=lift_gain_report['Random Selection'])
plt.title('Gain plot')
plt.xticks(lift_gain_report['Decil group'])
plt.yticks(round(lift_gain_report['Gain'],2))
([<matplotlib.axis.YTick at 0x18c6af534c0>, <matplotlib.axis.YTick at 0x18c6af52e60>, <matplotlib.axis.YTick at 0x18c647a1540>, <matplotlib.axis.YTick at 0x18c647a2200>, <matplotlib.axis.YTick at 0x18c647a22f0>, <matplotlib.axis.YTick at 0x18c647a33a0>, <matplotlib.axis.YTick at 0x18c647a2e30>, <matplotlib.axis.YTick at 0x18c647a0250>, <matplotlib.axis.YTick at 0x18c6af523b0>, <matplotlib.axis.YTick at 0x18c647c53c0>], [Text(0, 0.0, '0.00'), Text(0, 0.06, '0.06'), Text(0, 0.17, '0.17'), Text(0, 0.29, '0.29'), Text(0, 0.41, '0.41'), Text(0, 0.52, '0.52'), Text(0, 0.64, '0.64'), Text(0, 0.76, '0.76'), Text(0, 0.88, '0.88'), Text(0, 1.0, '1.00')])
Se poate observa cum valoarea indicatorului Gain variază pentru fiecare punct din grupul Decil. Pentru primul punct va fi egal cu 0, pentru al doilea punct va fi egal cu 0,06, pentru al treilea punct va fi egal cu 0,17, pentru al patrulea punct va fi egal cu 0,29, pentru al cincilea punct va fi egal cu 0,41, pentru al șaselea punct va fi egal cu 0,52, pentru al șaptelea punct va fi egal cu 0,64, pentru al optulea punct va fi egal cu 0,76, pentru al nouălea punct va fi egal cu 0,88, iar pentru al zecelea punct valoarea va ajunge la 1.
feat_imp=xgb.get_booster().get_score(importance_type='total_gain')
feat_imp
{'CLIENTNUM': 337.0828552246094,
'Customer_Age': 1529.3056640625,
'Dependent_count': 132.06858825683594,
'Months_on_book': 615.2696533203125,
'Total_Relationship_Count': 3694.20458984375,
'Months_Inactive_12_mon': 1212.135986328125,
'Contacts_Count_12_mon': 903.0044555664062,
'Credit_Limit': 815.6828002929688,
'Total_Used_Bal': 5177.43310546875,
'Total_Unused_Bal': 625.514892578125,
'Total_Amt_Chng_Q4_Q1': 2801.148193359375,
'Total_Trans_Amt': 11249.3623046875,
'Total_Trans_Ct': 11149.4453125,
'Total_Ct_Chng_Q4_Q1': 4541.037109375,
'Avg_Utilization_Ratio': 1186.154296875,
'Education_Level_College': 10.916389465332031,
'Education_Level_Doctorate': 3.8043665885925293,
'Education_Level_Graduate': 10.775384902954102,
'Education_Level_High School': 10.846713066101074,
'Education_Level_Post-Graduate': 5.802804946899414,
'Education_Level_Uneducated': 11.02813720703125,
'Education_Level_Unknown': 8.50363540649414,
'Marital_Status_Divorced': 9.740646362304688,
'Marital_Status_Married': 187.2605743408203,
'Marital_Status_Single': 52.36482620239258,
'Marital_Status_Unknown': 12.8047513961792,
'Income_Category_$120K +': 24.90848159790039,
'Income_Category_$40K - $60K': 1.5076053142547607,
'Income_Category_$60K - $80K': 64.86402130126953,
'Income_Category_$80K - $120K': 8.905508995056152,
'Income_Category_Less than $40K': 28.50957489013672,
'Income_Category_Unknown': 5.852596282958984}
feature_importance=pd.DataFrame()
feature_importance['Variable']=feat_imp.keys()
feature_importance['Importance Value']=feat_imp.values()
feature_importance['%Importance Feature']=feature_importance['Importance Value']/feature_importance['Importance Value'].sum()*100
feature_importance.sort_values(by=['Importance Value'],ascending=True)
| Variable | Importance Value | %Importance Feature | |
|---|---|---|---|
| 27 | Income_Category_$40K - $60K | 1.507605 | 0.003247 |
| 16 | Education_Level_Doctorate | 3.804367 | 0.008194 |
| 19 | Education_Level_Post-Graduate | 5.802805 | 0.012499 |
| 31 | Income_Category_Unknown | 5.852596 | 0.012606 |
| 21 | Education_Level_Unknown | 8.503635 | 0.018316 |
| 29 | Income_Category_$80K - $120K | 8.905509 | 0.019182 |
| 22 | Marital_Status_Divorced | 9.740646 | 0.020980 |
| 17 | Education_Level_Graduate | 10.775385 | 0.023209 |
| 18 | Education_Level_High School | 10.846713 | 0.023363 |
| 15 | Education_Level_College | 10.916389 | 0.023513 |
| 20 | Education_Level_Uneducated | 11.028137 | 0.023754 |
| 25 | Marital_Status_Unknown | 12.804751 | 0.027580 |
| 26 | Income_Category_$120K + | 24.908482 | 0.053651 |
| 30 | Income_Category_Less than $40K | 28.509575 | 0.061407 |
| 24 | Marital_Status_Single | 52.364826 | 0.112789 |
| 28 | Income_Category_$60K - $80K | 64.864021 | 0.139711 |
| 2 | Dependent_count | 132.068588 | 0.284464 |
| 23 | Marital_Status_Married | 187.260574 | 0.403342 |
| 0 | CLIENTNUM | 337.082855 | 0.726045 |
| 3 | Months_on_book | 615.269653 | 1.325234 |
| 9 | Total_Unused_Bal | 625.514893 | 1.347301 |
| 7 | Credit_Limit | 815.682800 | 1.756905 |
| 6 | Contacts_Count_12_mon | 903.004456 | 1.944988 |
| 14 | Avg_Utilization_Ratio | 1186.154297 | 2.554867 |
| 5 | Months_Inactive_12_mon | 1212.135986 | 2.610829 |
| 1 | Customer_Age | 1529.305664 | 3.293983 |
| 10 | Total_Amt_Chng_Q4_Q1 | 2801.148193 | 6.033415 |
| 4 | Total_Relationship_Count | 3694.204590 | 7.956976 |
| 13 | Total_Ct_Chng_Q4_Q1 | 4541.037109 | 9.780975 |
| 8 | Total_Used_Bal | 5177.433105 | 11.151713 |
| 12 | Total_Trans_Ct | 11149.445312 | 24.014876 |
| 11 | Total_Trans_Amt | 11249.362305 | 24.230088 |
explainer=shap.TreeExplainer(xgb)
shap_values=explainer.shap_values(X_test)
shap_values
array([[ 0.04779819, -0.16809815, -0.02426933, ..., 0.00058599,
0.01383665, -0.0003608 ],
[ 0.04676734, -0.11945987, -0.04687361, ..., -0.00479998,
0.01643804, -0.00041905],
[ 0.14659846, -0.10013846, -0.01258962, ..., 0.00660091,
0.01569512, -0.0005992 ],
...,
[ 0.08655098, -0.23870268, -0.02981216, ..., -0.00320706,
0.00855534, 0.0046726 ],
[ 0.02689816, -0.06554399, -0.02622731, ..., 0.00042218,
0.02002614, -0.00062051],
[-0.01122317, -0.10594083, -0.02742577, ..., 0.00455419,
0.02909476, 0.00176551]], dtype=float32)
shap.summary_plot(shap_values,X_test,plot_size=(22,11))
index=123
shap.initjs()
shap.force_plot(explainer.expected_value, shap_values[index],X_test.iloc[index])
index=79
shap.initjs()
shap.force_plot(explainer.expected_value, shap_values[index],X_test.iloc[index])
index=100
shap.initjs()
shap.force_plot(explainer.expected_value, shap_values[index],X_test.iloc[index])
index=219
shap.initjs()
shap.force_plot(explainer.expected_value, shap_values[index],X_test.iloc[index])
index=17
shap.initjs()
shap.force_plot(explainer.expected_value, shap_values[index],X_test.iloc[index])
Acuratețea probei
pr=pd.DataFrame()
pr['proba']=y_predict_proba_class_1
pr[pr['proba']==pr['proba'].max()]
| proba | |
|---|---|
| 1070 | 0.999916 |
Avem o acuratețe a rezultatelor de peste 99%.
data.to_csv('C:\\Users\\valen\\Desktop\\data\\dataset.csv')
with open('C:\\Users\\valen\\Desktop\\data\\final_model.pkl','wb') as file:
pickle.dump(best_model,file)